package nl.utwente.eemcs.common.helpers;


import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import nl.utwente.eemcs.graph.ActivityNode;
import nl.utwente.eemcs.graph.ConditionalEndNode;
import nl.utwente.eemcs.graph.ConditionalStartNode;
import nl.utwente.eemcs.graph.Edge;
import nl.utwente.eemcs.graph.EdgeType;
import nl.utwente.eemcs.graph.Graph;
import nl.utwente.eemcs.graph.LoopConditionalNode;
import nl.utwente.eemcs.graph.Node;
import nl.utwente.eemcs.graph.ParallelEndNode;
import nl.utwente.eemcs.graph.ParallelStartNode;
import nl.utwente.eemcs.graph.distribution.DistributionList;
import nl.utwente.eemcs.graph.distribution.DistributionLocation;

public class GraphBuilder {
	protected Graph outputGraph;
	protected HashMap<String, DataItem> dataItems;
	protected List<DataEdge> dataEdges;
	protected DistributionList distributionList;
	
	public GraphBuilder() {
		outputGraph = new Graph();
		dataItems = new HashMap<String, DataItem>();
		dataEdges = new ArrayList<DataEdge>();
		distributionList = new DistributionList();
	}
	
	/**
	 * Get the output graph
	 * @return The output graph
	 */
	public Graph getGraph() {
		return this.outputGraph;
	}
	
	public void setGraph(Graph g) {
		this.outputGraph = g;
	}

	public HashMap<String, DataItem> getDataItems() {
		return dataItems;
	}

	public List<DataEdge> getDataEdges() {
		return dataEdges;
	}
	
	public Node addNode(String id, String name, String type) {
		if (type.toLowerCase().endsWith("andsplit"))
			return addParallelStartNode(id, name);
		else if (type.toLowerCase().endsWith("andjoin"))
			return addParallelEndNode(id, name);
		else if (type.toLowerCase().endsWith("orsplit"))
			return addConditionalStartNode(id, name);	
		else if (type.toLowerCase().endsWith("orjoin"))
			return addConditionalEndNode(id, name);
		else if (type.toLowerCase().endsWith("loopnode"))
			return addLoopNode(id, name);		
		else
			return addBasicNode(id, name, type);
	}
	
	/**
	 * Add an activity node to the graph
	 * @param astNode Node in the AST
	 * @param name Name of the node
	 */
	protected ActivityNode addBasicNode(String name, String actName, String type) {
		// Create new node and add to graph and mark the AST with the created node
		ActivityNode node = new ActivityNode(name);
		node.addAttribute("type", type);
		node.addAttribute("name", actName);
		outputGraph.addNode(node);
		return node;
	}
	
	protected ParallelStartNode addParallelStartNode(String name, String actName) {
		ParallelStartNode node = new ParallelStartNode(name);
		node.addAttribute("type", "AndSplit");
		node.addAttribute("name", actName.replaceAll("\"", ""));		
		outputGraph.addNode(node);
		return node;
	}
	
	protected ParallelEndNode addParallelEndNode(String name, String actName) {
		ParallelEndNode node = new ParallelEndNode(name);
		node.addAttribute("type", "AndJoin");
		node.addAttribute("name", actName.replaceAll("\"", ""));		
		outputGraph.addNode(node);	
		return node;
	}
	
	protected ConditionalStartNode addConditionalStartNode(String name, String actName) {
		ConditionalStartNode node = new ConditionalStartNode(name);
		node.addAttribute("type", "OrSplit");
		node.addAttribute("name", actName.replaceAll("\"", ""));
		outputGraph.addNode(node);
		return node;
	}

	protected LoopConditionalNode addLoopNode(String name, String actName) {
		LoopConditionalNode node = new LoopConditionalNode(name);
		node.addAttribute("type", "OrSplit");
		node.addAttribute("name", actName.replaceAll("\"", ""));
		outputGraph.addNode(node);
		return node;
	}

	protected ConditionalEndNode addConditionalEndNode(String name, String actName) {
		ConditionalEndNode node = new ConditionalEndNode(name);
		node.addAttribute("type", "OrJoin");
		node.addAttribute("name", actName.replaceAll("\"", ""));
		outputGraph.addNode(node);
		return node;
	}

	/*protected ConditionalStartNode addLoopNode(String name, String actName, String evaluation) {
		ConditionalStartNode node = new ConditionalStartNode(name);
		node.addAttribute("type", "LoopNode");
		node.addAttribute("name", actName.replaceAll("\"", ""));
		outputGraph.addNode(node);
		return node;
	}*/

	public void addDataItem(String dataItem) {
		DataItem d = new DataItem(dataItem);
		this.dataItems.put(dataItem, d);
	}

	public void addDataEdge(String nodeName, String dataItem, String type) throws Exception {
		Node n = outputGraph.getNodeByName(nodeName);
		if (n == null)
			throw new Exception("The node \"" + n + "\" is not found in the graph");
		
		if (!dataItems.containsKey(dataItem))
			throw new Exception("Dataitem \"" + dataItem + "\" not declared");
		
		DataEdgeType deType = DataEdgeType.fromString(type);
		if (deType == null)
			throw new Exception("Invalid data edge type \"" + type + "\""); 
		
		dataEdges.add(new DataEdge(n, dataItems.get(dataItem), deType));		
	}

	public void addControlEdge(String from, String to, String condition) throws Exception {
		Node n1 = outputGraph.getNodeByName(from);
		Node n2 = outputGraph.getNodeByName(to);
		
		if (n1 == null)
			throw new Exception("The from node \"" + from + "\" is not found in the graph");
		if (n2 == null)
			throw new Exception("The to node \"" + to + "\" is not found in the graph");
		
		Edge e = new Edge(n1, n2, EdgeType.Control);
		if (condition != null && condition.replaceAll("\"", "").length() > 0)
			e.setLabel(condition.replaceAll("\"", ""));
		
		// Add edge to graph
		outputGraph.addEdge(e);
	}

	public DistributionList getDistributionList() {
		return this.distributionList;
	}

	public void addActivityDistribution(String nodeName, String location) throws Exception {
		DistributionLocation distrLoc = DistributionLocation.fromString(location);
		Node node = outputGraph.getNodeByName(nodeName);
		
		if (node == null)
			throw new Exception("The node \"" + nodeName + "\" is not found in the graph");
		
		distributionList.addNodeDistribution(node, distrLoc);
	}

	public void addDataDistributionRestriction(String dataItemName, String location) throws Exception {
		DistributionLocation distrLoc = DistributionLocation.fromString(location);
		DataItem d = this.dataItems.get(dataItemName);
		
		if (d == null)
			throw new Exception("The dataItem "+dataItemName+" is not found");
		
		distributionList.addDataRestriction(dataItemName.replaceAll("\"", ""), distrLoc);
	}

	/*public Node addLoopBranchNode(String id, String name, String type) {
		// Create new node and add to graph and mark the AST with the created node
		LoopConditionalNode node = new LoopConditionalNode(id);
		node.addAttribute("type", type);
		node.addAttribute("name", name);
		outputGraph.addNode(node);
		return node;
	}*/
}
